/******************************************************************************

    Copyright (C) 2010 Alessio Stalla (alessiostalla@gmail.com)

    This file is part of PrettyProlog.

    PrettyProlog is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    PrettyProlog is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrettyProlog; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    See the file COPYING for further details.

 *****************************************************************************/

package prettyprolog.engine.syspreds;

import java.lang.reflect.Method;
import java.util.List;

import prettyprolog.datatypes.CharString;
import prettyprolog.datatypes.Constant;
import prettyprolog.datatypes.JavaObject;
import prettyprolog.datatypes.Substitution;
import prettyprolog.datatypes.Symbol;
import prettyprolog.datatypes.Term;
import prettyprolog.datatypes.Variable;
import prettyprolog.engine.Engine;
import prettyprolog.engine.Theory;

/**
 * Implements the function/2 system predicate, used to register a function or to check whether
 * a given symbol names a function. The possible usage patterns are:
 * <ul>
 *   <li><code>function(name, Variable)</code> - binds Variable to the function called name, if it exists; else fails.</li>
 *   <li><code>function(name, class_name)</code> - registers an instance of <code>class_name</code> as a function called <code>name</code>.
 *   <code>class_name</code> must be a string naming a Java class that extends Function and that has a public static getInstance() method
 *   returning the singleton instance of the function. An error is signalled if class_name does not fulfill these requirements.</li>
 * </ul>
 * @author Alessio Stalla
 *
 */
public class Function extends SystemPredicate {

	private static final Function inst = new Function();

	private Function() {
		super(2);
	}

	/** Returns the only instance of this system predicate. */
	public static Function getInstance() {
		return inst;
	}

	protected SyspredEffect doCall(Engine en, Substitution subst, List<Term> args) throws SyspredException {
		Term function = args.get(1);
		Symbol name;
		if(args.get(0) instanceof CharString) {
			name = prettyprolog.datatypes.Namespace.current().intern(((CharString) args.get(0)).getString());
		} else if(args.get(0) instanceof Constant) {
			name = ((Constant) args.get(0)).getSymbol();
		} else {
			throw new SyspredException("Syspred name must be a constant or a string: " + args.get(0));
		}
		if(function instanceof CharString) {
			Theory th = en.getTheory();
			try {
				Class<?> c = Class.forName(((CharString) function).getString());
				if(prettyprolog.engine.Function.class.isAssignableFrom(c)) {
					Method getInstance = c.getMethod("getInstance");
					prettyprolog.engine.Function fn = (prettyprolog.engine.Function) getInstance.invoke(null);
					th.setFunction(name, fn);
					return new SyspredEffect(false, subst);
				} else {
					throw new SyspredException(function + " does not name a class extending SystemPredicate: " + c);
				}
			} catch(Exception e) {
				throw new SyspredException("Error registering function " + function, e);
			}
		} else if(function instanceof Variable) {
			prettyprolog.engine.Function fn = en.getTheory().getFunction(name);
			if(fn != null) {
				Substitution s2 = en.getUnificationAlgorithm().unify(function, new JavaObject(fn));
				return new SyspredEffect(false, subst.compose(s2));
			} else {
				return new SyspredEffect(true, subst);	
			}
		} else {
			throw new SyspredException(function + " must be a string or a variable");
		}
	}
	
	public boolean isBacktrackable() {
		return false;
	}

	public String toString() {
		return super.toString() + " (function/2)";
	}
}